package com.daodai.hjcjsystem.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.injector.methods.SelectById;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.daodai.hjcjsystem.domain.po.DeviceEntity;
import com.daodai.hjcjsystem.mapper.CollectionMapper;
import com.daodai.hjcjsystem.service.CollectionService;
import com.daodai.hjcjsystem.task.AsyncTaskService;
import com.daodai.hjcjsystem.utils.OpcUaClientUtil;
import lombok.RequiredArgsConstructor;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.nfunk.jep.JEP;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.swing.text.html.parser.Entity;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

@RequiredArgsConstructor
@Service
public class CollectionServiceImpl extends ServiceImpl<CollectionMapper, DeviceEntity> implements CollectionService {

    @Qualifier("opcUaClient37800")
    private final OpcUaClient opcUaClient37800;

    @Qualifier("opcUaClient37801")
    private final OpcUaClient opcUaClient37801;

    private final OpcUaClientUtil opcUaClientUtil;

    private final AsyncTaskService asyncTaskService;


    private final ThreadPoolTaskExecutor taskExecutor;



    @Override
    public void batchCollection() {

        //创建分页对象，每页20条
        int batchSize = 60;//每页数量
        int currentPage = 1;//当前页数
        int count = 1;//采集轮数
        //首先进行now表的删除初始化
        deleteByFlag();
        //利用while循环分批读取采集点位信息
        while (true) {
            //每一百条数据间隔1秒，减少异步数据库写入的任务堆积
            try {
                Thread.sleep(1800); //
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            int poolSize = taskExecutor.getPoolSize();//获取当前线程池中总线程数

            // 检查任务积压,做背压处理
            if (poolSize>=20) {
                try {
                    Thread.sleep(3000); // 背压：暂停采集
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            //创建分页参数
            IPage<DeviceEntity> Page = new Page<>(currentPage, batchSize);
            //调用设定的mapper分页方法，该方法为多表分页查询
            Page<DeviceEntity> result = baseMapper.selectBatch(Page);
            List<DeviceEntity> records = result.getRecords();
            for (DeviceEntity device : records) {
                //使用split分割取出ns和s
                String[] part1 = device.getPassageDeviceNode().split(";");
                int ns = Integer.parseInt(part1[0].split("=")[1]);
                String s = part1[1].split("=")[1];
                try {
                    DataValue value = null;//创建opc请求返回载体
                    //判断该点位属于哪一个服务器端口
                    if(device.getPort().equals("37800")){

                        //调用工具类中的采集函数，返回结果实体,请求的端口为37800
                        value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37800, ns, s);
                    }else {
                        //调用工具类中的采集函数，返回结果实体，请求的端口为37801
                        value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37801, ns, s);
                    }
                    //判断采集值是否符合规定
                    if (value.getStatusCode()!=null){
                        //采集值符合规定，且采集反馈状态为good,封装回实体类中
                        if (value.getStatusCode().isGood()||valueIsStandard(value,device)){
//                            //量程运算
//                            JEP jep = new JEP();
//                            double x = Double.parseDouble(value.getValue().getValue().toString());
//                            jep.addVariable("x",x ); // 设置变量值
//                            jep.parseExpression(device.getRegular()); // 解析表达式
//                            //封装回实体类中
//                            device.setValue(jep.getValueAsObject().toString());
                            device.setValue(value.getValue().getValue().toString());//重庆东不需要量程计算
                            System.out.println("读取节点" + s + "成功，节点值为：" + value.getValue().getValue().toString() +",时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
                        }else {
                            System.out.println("读取节点" + s + "失败，节点值为：null,时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                        }
                    }else {
                        System.out.println("读取节点" + s + "失败，节点值为：null,时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //插入表device_realtime_value操作
//            Boolean insertableState = saveToRealTimeValue(records);
//            if (insertableState){
//                System.out.println("数据插入表device_realtime_value成功！！");
//            }else System.out.println("数据插入表device_realtime_value失败！！");

            //调用异步线程将采集的数据插入表device_realtime_value
            asyncTaskService.saveToRealTimeValue(records);

            //在第一轮采集过程中，程序会通过插入的方式首次在表device_realtime_value_now中保存数据
            if (count==1){
                //获取标志位信息
                int flag = baseMapper.selectFlag();
                //通过标志位判断表中是否有数据
                if (flag==0){
                    //增加数据创建时间和更新时间(因为程序每次启动只执行一次插入操作，所以不用异步线程)
                    records.forEach(deviceEntity -> {
                        deviceEntity.setCreateTime(LocalDateTime.now());
                        deviceEntity.setModifyTime(LocalDateTime.now());
                    });

                    //插入操作
                    baseMapper.saveToNowTable(records);

                }
            }else if (count==2){
                //查询数据库表中的标志位信息
                int flag = baseMapper.selectFlag();
                //做标志位判断，如果为0，将标志位信息置为1
                if (flag==0){

                    //将标志位置为1
                    baseMapper.changeFlag();
                    System.out.println("数据首次插入表device_realtime_value_now成功！！");
                }else {

                    //如果标志位已经为1，说明第一次插入已经完成，只用在第二轮开始后执行更新操作(调用异步线程执行操作)
                    asyncTaskService.updateToNowTable(records);
                }
            }else {
                //第二轮之后均只执行更新操作(调用异步线程执行操作)
                asyncTaskService.updateToNowTable(records);
            }
            //判断是否还有下一页
            if (currentPage>=result.getPages()){
                //没有下一页，将页数重新置为1，同时增加轮数，开始新一轮轮询
                count += 1;
                currentPage =1;
            }else {
                //还有下一页，页数+1
                currentPage++;
            }
        }
    }

    /**
     * 将采集数据插入到device_realtime_value表中
     * @param deviceEntityList
     * @return
     */
    public Boolean saveToRealTimeValue(List<DeviceEntity> deviceEntityList){
        //增加数据创建时间和更新时间
        deviceEntityList.forEach(deviceEntity -> {
            deviceEntity.setCreateTime(LocalDateTime.now());
            deviceEntity.setModifyTime(LocalDateTime.now());
        });
        return saveBatch(deviceEntityList);
    }

    /**
     * 更新采集值到表device_realtime_value_now中
     * @param deviceEntityList
     */
    public void updateToNowTable(List<DeviceEntity> deviceEntityList){
        //仅设置更新时间
       deviceEntityList.forEach(device ->{
           device.setModifyTime(LocalDateTime.now());
       });
       baseMapper.updateToNowTable(deviceEntityList);
    }
    /**
     * 判断所采集的值是否在规定量程范围内
     * @param value
     * @param device
     * @return
     */
    public Boolean valueIsStandard(DataValue value,DeviceEntity device){

        //取出实体类中查询到的数值范围
        String[] part = device.getValueRange().split("~");
        //转换得到的数值范围下限
        int minRange = Integer.parseInt(part[0]);
        //转换得到数值范围上限
        int maxRange = Integer.parseInt(part[1]);

        //判断采集到的value值是否为空
        if (value.getValue().getValue()==null){
            return false;
        }

        //如果采集到的值为布尔值，则不用判断量程
        if (value.getValue().getValue() instanceof Boolean){
            return true;
        }
        //判断采集到的数据类型是否为浮点数
        if (value.getValue().getValue() instanceof Float){
            float data = Float.parseFloat(value.getValue().getValue().toString());
            //与范围上下限进行比较判断，返回结果
            if (data>=minRange || data<=maxRange){
                return true;
            }else {
                return false;
            }
        }
        //将采集到的值进行类型强转
        int data = Integer.parseInt(value.getValue().getValue().toString());

        //与范围上下限进行比较判断，返回结果
        if (data>=minRange || data<=maxRange){
            return true;
        }else {
            return false;
        }

    }

    /**
     * 初始化时，进行删表操作
     */
   public void deleteByFlag(){
       //获取标志位信息
       int flag = baseMapper.selectFlag();
       if (flag==0){
           //无论有无数据存在，都进行删除操作
           baseMapper.deleteAll();
           System.out.println("系统初始化删表成功！！！");
       }
   }

    /**
     * 当日数据备份
     */
    @Override
    public void backup() {

        //获取头一天的日期
        LocalDate dateNow = LocalDate.now().minusDays(1);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String dateBefore = dateNow.format(formatter);//建表日期后缀

        //创建备份表
        Connection con = null;
        PreparedStatement ps = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            con = DriverManager.getConnection("jdbc:mysql://49.232.7.176:3306/yethannyhj?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true","root","jdims//123");
            String sql = "create table"+" subtable_value" +dateBefore+"(" +
                    "sid int primary key not null auto_increment comment '主键 序号 自增'," +
                    "passage_device_node varchar(100) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '通道.设备.点位'," +
                    "passage varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '通道'," +
                    "node_id varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '点位id'," +
                    "node_describe varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '点位描述'," +
                    "device_type_id varchar(100) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '设备类型id'," +
                    "device_type_name varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '设备类型名称'," +
                    "parent_dev_id varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '所属设别id'," +
                    "parent_dev_name varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '所属设备名称'," +
                    "device_id varchar(100) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '设备id'," +
                    "device_name varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '设备名称'," +
                    "value varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '信号值'," +
                    "value_type varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '信号值属性（dict_device_valuetype）'," +
                    "collect_time datetime null default null comment '采集时间'," +
                    "state bit(1) null default null comment '是否有效 0-无效 1-有效'," +
                    "del_flag bit(1) null default b'0' comment '逻辑删除 0-否 1-是'," +
                    "remarks varchar(200) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '备注'," +
                    "create_time datetime null default null comment '创建时间'," +
                    "create_user_id varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '创建人id'," +
                    "create_user varchar(40) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '创建人'," +
                    "create_ip varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '创建人ip'," +
                    "modify_time datetime null default null comment '最后更新时间'," +
                    "modify_user varchar(40) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '最后更新人'," +
                    "modify_ip varchar(50) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '最后更新ip'," +
                    "modify_user_id varchar(20) character set  utf8mb4 collate utf8mb4_unicode_ci null default null comment '最后更新人id'," +
                    "index deviceIdIndex (device_type_id asc ,device_id asc ) using btree ," +
                    "index idx_nodeid_deviceid_collectiontime (node_id asc,device_id asc ,collect_time asc) using btree comment '统计使用')" ;
            ps = con.prepareStatement(sql);
            ps.execute();
            ps.close();
            con.close();
        }catch (Exception e){
            e.printStackTrace();
        }

        //往备份表中导入头一天的数据

        LocalDateTime dateStartTime = LocalDate.now().minusDays(1).atStartOfDay();//获取头一天开始时间
        LocalDateTime dateEndTime = LocalDate.now().minusDays(1).atTime(LocalTime.MAX);//获取头一天结束时间
        //获取插入表名
        String tableName = "yethannyhj.subtable_value" + dateBefore;//新建的动态表名
        //调用批量处理数据方法
        batchInsertDateRange(dateStartTime,dateEndTime,1000,tableName);


    }

    /**
     * 删除表中数据
     */
    @Override
    public void deleteTask() {
        //先确定删除日期，获取头一天的数据
        LocalDateTime dateStartTime = LocalDate.now().minusDays(1).atStartOfDay();//获取头一天开始时间
        LocalDateTime dateEndTime = LocalDate.now().minusDays(1).atTime(LocalTime.MAX);//获取头一天结束时间

        //删除头一天数据
        batchDeleteDateRange(dateStartTime,dateEndTime,1000);

    }

    /**
     * 条件分页查询通用方法
     * @param startTime
     * @param endTime
     * @param pageNum
     * @param pageSize
     * @return
     */
    public Page<DeviceEntity> queryByDateRangeLambda(LocalDateTime startTime, LocalDateTime endTime, int pageNum, int pageSize) {
        Page<DeviceEntity> page = new Page<>(pageNum, pageSize);

        LambdaQueryWrapper<DeviceEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.between(DeviceEntity::getCreateTime, startTime, endTime)
                .orderByAsc(DeviceEntity::getCreateTime);

        return baseMapper.selectPage(page, wrapper);
    }

    /**
     * 分批次转存数据
     * @param start
     * @param end
     * @param batchSize
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 明确事务边界
    public void batchInsertDateRange(LocalDateTime start, LocalDateTime end, int batchSize, String tableName) {
        int currentPage = 1;
        Page<DeviceEntity> page;

        do {
            page = queryByDateRangeLambda(start, end, currentPage, batchSize);
            if (page.getRecords()!=null && page.getRecords().size()!=0){

                batchInsert(currentPage,tableName,page.getRecords());
            }
            currentPage++;
        } while(page.hasNext()); // 判断是否还有下一页
        //没有下一页则定时数据转存成功
        System.out.println("定时数据转存成功！！！");
    }

    /**
     * 分批次删除数据
     * @param start
     * @param end
     * @param batchSize
     */

    public void batchDeleteDateRange(LocalDateTime start, LocalDateTime end, int batchSize) {
        int currentPage = 1;
        Page<DeviceEntity> page;

        do {
            page = queryByDateRangeLambda(start, end, currentPage, batchSize);
            if (page.getRecords()!=null && page.getRecords().size()!=0){

                batchDelete(currentPage,page.getRecords());
            }
            currentPage++;
        } while(page.hasNext()); // 判断是否还有下一页
        //没有下一页则定时删除操作完成
        System.out.println("定时数据删除完成！！！！");
    }

    /**
     * 提取插入数据库方法进行事务独立管理
     * @param currentPage
     * @param deviceEntityList
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 明确事务边界
    public void batchInsert(int currentPage,String tableName,List<DeviceEntity> deviceEntityList){
        System.out.println("第" + currentPage +"批数据转存开始！时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        //数据批量插入数据库
        baseMapper.saveToSubtable(tableName,deviceEntityList);
        System.out.println("第" + currentPage +"批数据转存成功！！时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

    }

    /**
     * 提取删除数据库方法进行事务独立管理
     * @param currentPage
     * @param deviceEntityList
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW) // 明确事务边界
    public void batchDelete(int currentPage,List<DeviceEntity> deviceEntityList){
        System.out.println("第" + currentPage +"批数据删除开始！时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

        LocalDateTime deleteStartTime = deviceEntityList.get(0).getCreateTime();//删除开始时间
        LocalDateTime deleteEndTime = deviceEntityList.get(deviceEntityList.size()).getCreateTime();//删除结束时间
        //数据批量删除
        baseMapper.deleteByTime(deleteStartTime,deleteEndTime);
        System.out.println("第" + currentPage +"批数据删除成功！！时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
    }
}
